#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QDebug>
#include <QKeyEvent>
#include <QMenu>
#include "plcgraphicsscene.h"

PlcGraphicsScene::PlcGraphicsScene(QGraphicsView* view, QObject* parent)
    : QGraphicsScene(parent)
    , m_mode(Normal)
    , m_typeToCreate(PlcInstructionType::NormallyOpen)
    , m_view(view)
    , m_gridSize(83, 53)
    , m_showGrid(false)
{
    Q_ASSERT(m_view);
}

// 重建每行的锚点
void PlcGraphicsScene::rebuildRowAnchors()
{
    // 清除旧锚点
    for (auto* it : m_rowAnchors)
    {
        if (it)
        {
            removeItem(it);
            delete it;
        }
    }
    m_rowAnchors.clear();

    const QRectF r = sceneRect();
    const double h = m_gridSize.height();
    const double oy = 0.0;                 // 与 drawBackground 一致
    const int rows = qCeil((r.height() - oy) / h) + 1;

    for (int i = 0; i < rows; ++i)
    {
        const double cy = oy + i * h + h * 0.5; // 每行中心
        // 让圆心恰在 x=0
        QRectF er(-kAnchorRadius, cy - kAnchorRadius, kAnchorRadius * 2.0, kAnchorRadius * 2.0);
        auto* dot = new QGraphicsEllipseItem(er);
        dot->setBrush(Qt::black);
        dot->setPen(Qt::NoPen);
        dot->setZValue(0.5);                  // 比背景略高
        dot->setData(0, QStringLiteral("anchor")); // 识别标记
        addItem(dot);

        m_rowAnchors.push_back(dot);
    }
}

void PlcGraphicsScene::clearAll()
{
    // 统一清空场景内所有图元
    QGraphicsScene::clear();

    // 不去访问/删除 m_rowAnchors 里旧指针（它们已无效）
    // 直接清空指针数组，避免 rebuildRowAnchors() 里二次 delete
    m_rowAnchors.clear();
}

// 网格中心吸附
QPointF PlcGraphicsScene::cellCenterAt(const QPointF& scenePos) const
{
    double w = m_gridSize.width();
    double h = m_gridSize.height();

    double ox = m_gridOriginX;      // 与 drawBackground 的 left+35 对齐
    double oy = 0.0;                // 如需纵向也偏移，可加 m_gridOriginY
    double col = qRound((scenePos.x() - ox) / w);
    double row = qRound((scenePos.y() - oy) / h);
    return QPointF(ox + col * w + w * 0.5, oy + row * h + h * 0.5);
}

QPointF PlcGraphicsScene::snapTopLeftToCellCenter(const QPointF& scenePos, const QSizeF& item) const
{
    QPointF center = cellCenterAt(scenePos);
    return center - QPointF(item.width() * 0.5, item.height() * 0.5); // 把“中心”转换为“左上角”
}

void PlcGraphicsScene::setGridOriginX(double x)
{
    m_gridOriginX = x;
}

double PlcGraphicsScene::gridOriginX() const
{
    return m_gridOriginX;
}

// 复制：保存类型、尺寸、ON状态
void PlcGraphicsScene::copyFrom(const PlcElement* src)
{
    if (!src) return;
    m_clip.valid = true;
    m_clip.type  = src->instructionType();
    m_clip.size  = QSizeF(src->boundingRect().width(), src->boundingRect().height());
    m_clip.on    = src->isOn();

    // 拷贝属性对话框设置的值
    m_clip.prefix = src->propPrefix();
    m_clip.v1     = src->propValue();
    m_clip.v2     = src->propValue2();
}

// 是否可粘贴
bool PlcGraphicsScene::canPaste() const
{
    return m_clip.valid;
}

// 在指定位置粘贴一个新元件（落在网格中心/左上吸附）
PlcElement* PlcGraphicsScene::pasteAt(const QPointF& scenePos)
{
    if (!m_clip.valid) return nullptr;

    auto *item = new PlcElement();
    item->setInstructionType(m_clip.type);
    item->setSize(m_clip.size.width(), m_clip.size.height());
    item->setOn(m_clip.on);

    // 还原标题对话框的值
    item->setPropPrefix(m_clip.prefix);
    item->setPropValue(m_clip.v1);
    item->setPropValue2(m_clip.v2);

    const QPointF topLeft = snapTopLeftToCellCenter(scenePos, m_clip.size);
    item->setPos(topLeft);
    addItem(item);

    emit elementCreated(item);
    return item;
}

bool PlcGraphicsScene::insertConnection(PlcElement* element)
{
    if (!element) return false;

    // 以元件中心做投影判断
    const QPointF center = element->mapToScene(QPointF(element->boundingRect().width()/2.0,
                                                       element->boundingRect().height()/2.0));

    // 允许一定容差
    constexpr qreal kHitTol = 10.0;

    auto isHorizontal = [](const QPointF& a, const QPointF& b){
        return std::abs(a.y() - b.y()) <= 0.5; // 像素级容差认定为水平
    };

    // 遍历场景里的 ConnectionItem，寻找最近的水平段
    ConnectionItem* bestConn = nullptr;
    QPointF         bestProj;
    qreal           bestDist = std::numeric_limits<qreal>::max();

    for (QGraphicsItem* gi : items(element->sceneBoundingRect().adjusted(-30,-30,30,30)))
    {
        auto* conn = qgraphicsitem_cast<ConnectionItem*>(gi);
        if (!conn) continue;

        // 线的两端（无论直线/折线，p1/p2 都是总起终点）
        const QPointF p1 = conn->line().p1();
        const QPointF p2 = conn->line().p2();

        // 构造要测试的段列表：直线只有一段；折线有两段（p1->bend, bend->p2）
        struct Seg { QPointF a,b; };
        QVector<Seg> segs;
        if (conn->isSelected()) {} // 无特殊逻辑，只是避免编译告警
        // 通过 shape() 我们无法直接拿 bend，这里直接通过 ConnectionItem 提供的接口判断
        // ——我们没有对外的 bend 访问器，所以用简单策略：
        // 若 conn 为折线：水平段必然位于 (p1.x==bend.x ? bend->p2 : p1->bend) 中的一段；
        // 这里我们只用“渲染端点直线”处理，因为 setPolylineMode 后 line().p1()/p2() 仍是总端点，
        // 插入时按“总起点→元件→总终点”拆分即可。
        // 但为了命中精度，依然需要用真实水平段做投影：
        //
        // 简化：如果是折线，就按两段近似为：
        //   水平段 y = p1.y() 或 p2.y()，x 范围 [min(p1.x(), p2.x()) , max(...) ]
        // 在 L 形里恰有一段水平一段垂直。我们用两段候选：
        segs.push_back({p1, QPointF(p2.x(), p1.y())}); // 假想水平段：y = p1.y()
        segs.push_back({QPointF(p1.x(), p2.y()), p2}); // 假想水平段：y = p2.y()

        // 如果本来就是直线，上面的两段中只有一段会与真实线重合（另一段是对角）
        // 下面用 isHorizontal() 过滤掉非水平段
        for (const auto& s : segs)
        {
            if (!isHorizontal(s.a, s.b)) continue;

            const qreal x1 = std::min(s.a.x(), s.b.x());
            const qreal x2 = std::max(s.a.x(), s.b.x());
            const qreal y  = s.a.y();

            // 投影到水平段
            QPointF proj(qBound(x1, center.x(), x2), y);
            const qreal dist = QLineF(center, proj).length();

            if (dist < bestDist && dist <= kHitTol)
            {
                bestDist  = dist;
                bestProj  = proj;
                bestConn  = conn;
            }
        }
    }

    if (!bestConn) return false; // 未命中

    // 拆分 bestConn
    // 记录旧线两端信息
    PlcElement* startItem = bestConn->startItem();
    PlcElement* endItem   = bestConn->endItem();
    const bool  startIsAnchor = bestConn->startIsAnchor();
    const bool  endIsAnchor   = bestConn->endIsAnchor();
    const auto  startSide     = bestConn->startSide();
    const auto  endSide       = bestConn->endSide();

    // 用端点坐标保存锚点场景位置（如果端点是锚点）
    const QPointF p1 = bestConn->line().p1();
    const QPointF p2 = bestConn->line().p2();

    // 这里顺便判断一下：旧线是否“看起来像 L 形”
    const bool originalLooksLikeL = std::abs(p1.y() - p2.y()) > 0.5;

    // 从两端元件的连接列表中移除旧线
    if (!startIsAnchor && startItem) startItem->removeConnection(bestConn);
    if (!endIsAnchor   && endItem)   endItem->removeConnection(bestConn);

    // 删除旧线
    removeItem(bestConn);
    delete bestConn;
    bestConn = nullptr;

    // 生成：左段 → 元件(Left)
    if (startIsAnchor)
    {
        auto* c1 = new ConnectionItem(element, ConnectionItem::Left, p1);
        addItem(c1);
        element->addConnection(c1);
        c1->updatePosition();
    }
    else if (startItem)
    {
        auto* c1 = new ConnectionItem(startItem, startSide, element, ConnectionItem::Left);
        addItem(c1);
        startItem->addConnection(c1);
        element->addConnection(c1);
        c1->updatePosition();
    }

    // 生成：元件(Right) → 右段
    ConnectionItem* c2 = nullptr;
    if (endIsAnchor)
    {
        c2 = new ConnectionItem(element, ConnectionItem::Right, p2);
        addItem(c2);
        element->addConnection(c2);
        c2->updatePosition();
    }
    else if (endItem)
    {
        c2 = new ConnectionItem(element, ConnectionItem::Right, endItem, endSide);
        addItem(c2);
        element->addConnection(c2);
        endItem->addConnection(c2);
        c2->updatePosition();
    }

    // 若原线为 L 形，或右半段本身非水平/垂直，则把右半段设为折线（水平->垂直）
    if (c2)
    {
        const QPointF s = c2->line().p1();  // 右半段起点（元件右侧圆点）
        const QPointF e = c2->line().p2();  // 右半段终点（可能是锚点或元件端）
        const bool segLooksDiagonal = (std::abs(s.x() - e.x()) > 0.5) && (std::abs(s.y() - e.y()) > 0.5);

        if (originalLooksLikeL || segLooksDiagonal)
        {
            const QPointF bend(e.x(), s.y());   // 先水平到 end.x，再垂直到 end.y
            c2->setPolylineMode(true, bend);
        }
    }

    return true;
}

void PlcGraphicsScene::setMode(Mode mode)
{
    m_mode = mode;
}

void PlcGraphicsScene::setInstructionTypeToCreate(PlcInstructionType type)
{
    m_typeToCreate = type;
}

void PlcGraphicsScene::deleteSelectedItems()
{
    const auto sel = selectedItems();

    // 先删元件（它们会在析构中安全拆线）
    for (QGraphicsItem* it : sel)
    {
        if (it->type() == PlcElement::Type)
        {
            auto* elem = static_cast<PlcElement*>(it);
            elem->removeConnections();         // 可留作显式拆线
            removeItem(elem);
            delete elem;
        }
    }

    // 再删仍然存在的连线（带 scene() 校验）
    for (QGraphicsItem* it : sel)
    {
        if (it->type() == ConnectionItem::Type)
        {
            auto* conn = static_cast<ConnectionItem*>(it);
            if (!conn || conn->scene() != this) continue;  // 防止二次删除
            if (!conn->startIsAnchor() && conn->startItem()) conn->startItem()->removeConnection(conn);
            if (!conn->endIsAnchor() && conn->endItem())   conn->endItem()->removeConnection(conn);
            removeItem(conn);
            delete conn;
        }
    }
    emit sceneContentChanged();
}

void PlcGraphicsScene::startConnectionDrag(PlcElement* element, bool fromRight)
{
    m_dragElement = element;
    m_dragFromRight = fromRight;

    // 起点改为“真实端点中心”，保证拖拽线从黑点出发
    QPointF p0 = fromRight ? element->mapToScene(element->rightConnectionPoint())
                           : element->mapToScene(element->leftConnectionPoint());
    m_tempLine = new QGraphicsLineItem(QLineF(p0, p0));
    m_tempLine->setPen(QPen(Qt::black, 2));
    addItem(m_tempLine);
}

void PlcGraphicsScene::updateConnectionDrag(const QPointF& scenePos)
{
    if (m_tempLine && m_dragElement)
    {
        QLineF newLine = m_tempLine->line();
        newLine.setP2(scenePos);
        m_tempLine->setLine(newLine);
    }
}

void PlcGraphicsScene::endConnectionDrag(const QPointF& scenePos)
{
    if (!m_tempLine || !m_dragElement) return;

    PlcElement* endElement = nullptr;
    bool endIsLeft = false;
    QPointF hitAnchorCenter;
    bool hitAnchor = false;

    const QList<QGraphicsItem*> under = items(scenePos);

    // 1、优先：命中元件端点
    for (QGraphicsItem* it : under)
    {
        if (it->type() == PlcElement::Type)
        {
            auto* elem = qgraphicsitem_cast<PlcElement*>(it);
            const QPointF localPos = elem->mapFromScene(scenePos);
            bool isLeft = false;
            if (elem->isConnectionPointHit(localPos, isLeft))
            {
                endElement = elem;
                endIsLeft = isLeft;
                break;
            }
        }
    }

    // 2、其次：命中行锚点
    if (!endElement)
    {
        for (QGraphicsItem* it : under)
        {
            if (it->data(0).toString() == QStringLiteral("anchor"))
            {
                hitAnchorCenter = it->sceneBoundingRect().center();
                hitAnchor = true;
                break;
            }
        }
    }

    // 3、命中“线段”（ConnectionItem）
    ConnectionItem* hitLine = nullptr;
    QPointF projected;
    if (!endElement && !hitAnchor)
    {
        for (QGraphicsItem* it : under)
        {
            if (it->type() == ConnectionItem::Type)
            {
                hitLine = qgraphicsitem_cast<ConnectionItem*>(it);
                if (hitLine) {
                    // 计算 scenePos 到该线段的投影点
                    const QLineF L = hitLine->line();
                    const QPointF A = L.p1();
                    const QPointF B = L.p2();
                    const QPointF AP = scenePos - A;
                    const QPointF AB = B - A;
                    const qreal ab2 = AB.x()*AB.x() + AB.y()*AB.y();
                    qreal t = 0.0;
                    if (ab2 > 0.0)
                    {
                        t = (AP.x()*AB.x() + AP.y()*AB.y()) / ab2;
                        t = std::clamp<qreal>(t, 0.0, 1.0);
                    }
                    projected = A + t * (B - A);
                }
                break;
            }
        }
    }

    // 建立连接
    if (endElement && endElement != m_dragElement)
    {
        ConnectionItem::Side startSide = m_dragFromRight ? ConnectionItem::Right : ConnectionItem::Left;
        ConnectionItem::Side endSide   = endIsLeft ? ConnectionItem::Left   : ConnectionItem::Right;

        auto* conn = new ConnectionItem(m_dragElement, startSide, endElement, endSide);
        addItem(conn);
        m_dragElement->addConnection(conn);
        endElement->addConnection(conn);
        conn->updatePosition();
    }
    else if (hitAnchor)
    {
        ConnectionItem::Side startSide = m_dragFromRight ? ConnectionItem::Right : ConnectionItem::Left;
        auto* conn = new ConnectionItem(m_dragElement, startSide, hitAnchorCenter);
        addItem(conn);
        m_dragElement->addConnection(conn);
        conn->updatePosition();
    }
    else if (hitLine)  // 命中线段
    {
        // 起点：元件端点；终点：线段投影点（静态锚点）
        ConnectionItem::Side startSide = m_dragFromRight ? ConnectionItem::Right : ConnectionItem::Left;
        auto* conn = new ConnectionItem(m_dragElement, startSide, projected);
        addItem(conn);
        m_dragElement->addConnection(conn);

        // 设为“折线显示”：规则采用“水平 -> 垂直”L 形
        // bend = (end.x, start.y)
        // 通过 updatePosition() 先求出当前的 start/end，再设置 bend
        conn->updatePosition();
        const QPointF s = conn->line().p1();
        const QPointF e = conn->line().p2();
        const QPointF bend(e.x(), s.y());
        conn->setPolylineMode(true, bend);
    }

    // 清理拖线
    removeItem(m_tempLine);
    delete m_tempLine;
    m_tempLine = nullptr;

    m_dragElement = nullptr;
    m_dragFromRight = false;
}

void PlcGraphicsScene::drawBackground(QPainter* painter, const QRectF& rect) {
    if (!m_showGrid) return;
    painter->setPen(QPen(QColor(220, 220, 220), 1));

    double w  = m_gridSize.width();
    double h  = m_gridSize.height();
    double ox = m_gridOriginX;
    double oy = 0.0;
    // 计算与原点对齐后的第一条网格线位置
    double firstV = ox + std::floor((rect.left() - ox) / w) * w;
    double firstH = oy + std::floor((rect.top()  - oy) / h) * h;
    // 竖线
    for (int x = firstV; x <= rect.right(); x += w)
    {
        painter->drawLine(QLineF(x, rect.top(), x, rect.bottom()));
    }
    // 横线
    for (int y = firstH; y <= rect.bottom(); y += h)
    {
        painter->drawLine(QLineF(rect.left(), y, rect.right(), y));
    }
}

void PlcGraphicsScene::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace)
    {
        // 复制一份，避免遍历时修改原列表
        const auto selected = selectedItems();

        bool deletedSomething = false;

        // 第一阶段：删除选中的元件（它们会自行安全拆线）
        for (QGraphicsItem* it : selected)
        {
            if (it->type() == PlcElement::Type)
            {
                auto* elem = static_cast<PlcElement*>(it);
                elem->removeConnections();   // 先拆线
                removeItem(elem);
                delete elem;
                deletedSomething = true;
            }
        }

        // 第二阶段：删除仍然存在的独立选中连线（可能没有元件被选中、或用户只选了线）
        for (QGraphicsItem* it : selected)
        {
            if (it->type() == ConnectionItem::Type)
            {
                auto* conn = static_cast<ConnectionItem*>(it);

                // 如果在第一阶段已经被删除，scene() 会是 nullptr，直接跳过，防二次删除
                if (!conn || conn->scene() != this) continue;

                // 从两端元件的连接列表里移除（如果端点是元件）
                if (!conn->startIsAnchor() && conn->startItem())
                {
                    conn->startItem()->removeConnection(conn);
                }
                if (!conn->endIsAnchor() && conn->endItem())
                {
                    conn->endItem()->removeConnection(conn);
                }
                removeItem(conn);
                delete conn;
                deletedSomething = true;
            }
        }
        if (deletedSomething)
        {
            emit sceneContentChanged();
        }
        event->accept();
        return;
    }

    QGraphicsScene::keyPressEvent(event);
}

void PlcGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
    // 命中检查：若点在元素上，则由元素自己的 contextMenuEvent 处理
    QGraphicsItem* hit = itemAt(event->scenePos(), QTransform());
    if (hit)
    {
        // 交由该 item 处理
        QGraphicsScene::contextMenuEvent(event);
        return;
    }

    // 空白处：只弹出“粘贴”
    QMenu menu;
    QAction *actPaste = menu.addAction(QStringLiteral("粘贴"));
    if (!canPaste())
    {
        actPaste->setEnabled(false);
    }

    QAction *chosen = menu.exec(event->screenPos());
    if (chosen == actPaste && canPaste())
    {
        pasteAt(event->scenePos());
        event->accept();
        return;
    }

    QGraphicsScene::contextMenuEvent(event);
}

// 建元件：落在网格中心
void PlcGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if (event->button() == Qt::LeftButton && m_mode == CreateItem)
    {
        auto *item = new PlcElement();
        item->setInstructionType(m_typeToCreate);

        const QSizeF itemSize(item->boundingRect().width(), item->boundingRect().height());
        const QPointF placeTopLeft = snapTopLeftToCellCenter(event->scenePos(), itemSize);

        item->setPos(placeTopLeft);
        addItem(item);

        emit elementCreated(item);
        setMode(Normal);
        event->accept();
        return;
    }
    QGraphicsScene::mousePressEvent(event);
}

void PlcGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    QGraphicsScene::mouseMoveEvent(event);
}

void PlcGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    QGraphicsScene::mouseReleaseEvent(event);
}
